package server;

import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import cdautils.TriageTag;
import cdautils.CDAParser;

import persistence.DAOException;
import persistence.DAOTriageTag;
import persistence.DAOTriageTagHSQLDB;

import utilities.Logger;

/**
 * This class implements the all REST-requests as specified in our specification-document.
 * 
 * @author Felix Schuller
 */

@Path("/patients")
public class RestApi {
	
	  private static final Logger log = Logger.getLogger(RestApi.class);

	  /**
	   * GET: Returns a formated XML (as Document) containing information about all persisted TriageTags.
	   * Query can be filtered for triagekategorie and behandlungsstatus
	   * @param triagekategorie triagekategorie to filter for
	   * @param behandlung behandlungsstatus to filter for
	   * @return Formated XML containing information about all persisted TriageTags
	   */
	  @GET
	  @Produces( MediaType.TEXT_XML )
	  public Document getAllTriageTags(@QueryParam("triagekategorie") String triagekategorie,@QueryParam("behandlung") String behandlung)
	  {
			try {
				
				log.i("GET-patientlist: got GET of patientlist");			
				//Query data for patientlist
				DAOTriageTag dtt = DAOTriageTagHSQLDB.getInstance();
				TriageTag.TriageCategory filterTriagekategorie = null;
				if (triagekategorie != null) {
					filterTriagekategorie = TriageTag.TriageCategory.getFromString(triagekategorie);
					log.i("GET-patientlist: filtering for triagekategorie = " + triagekategorie);	
				}
				TriageTag.TreatmentStatus filterBehandlung = null;
				if (behandlung != null) {
					filterBehandlung = TriageTag.TreatmentStatus.getFromString(behandlung);
					log.i("GET-patientlist: filtering for behandlungsstatus = " + behandlung);	
				}
				Set<TriageTag> tags = dtt.getTags(filterTriagekategorie, filterBehandlung);
				if (tags.isEmpty()) {
					log.i("GET-patientlist: no matching patients ... 404");
					throw new WebApplicationException(404);
				}
				
				//Create patientlist document
				DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
				
				Document doc = docBuilder.newDocument();
				Element rootElement = doc.createElement("patientlist");
				doc.appendChild(rootElement);
				
				
				//Build patientlist
				for (TriageTag t : tags) {
					Element patient = doc.createElement("patient");
					rootElement.appendChild(patient);
					
					Element name = doc.createElement("name");
					patient.appendChild(name);
					Element given = doc.createElement("given");
					given.appendChild(doc.createTextNode(t.getGiven()));
					name.appendChild(given);
					Element family = doc.createElement("family");
					family.appendChild(doc.createTextNode(t.getFamily()));
					name.appendChild(family);
					
					Element triagekategorieEl = doc.createElement("triagekategorie");
					triagekategorieEl.appendChild(doc.createTextNode(t.getTriagekategorie().toString()));
					patient.appendChild(triagekategorieEl);
					
					Element url = doc.createElement("url");
					url.appendChild(doc.createTextNode("http://88.116.105.228:30102/restApi/patients/" + t.getDocument_uid()));
					patient.appendChild(url);
				}
				
				log.i("GET-patientlist: matching patients found, responding with HL7-CDA/XML.");
				return doc;
				
			} catch (ParserConfigurationException e) {
				log.e("ParserConfigurationException: " + e.getMessage());
				throw new WebApplicationException(404);
			} catch (DAOException e) {
				log.e("DAOException: " + e.getMessage());
				throw new WebApplicationException(404);
			}
	  }
	  
	  /**
	   * GET: Returns XML/HL7 CDA representation of a TriageTag with given uid
	   * @param uid Uid of wantedTriageTag
	   * @return XML/HL7 CDA representation of TriageTag with given uid
	   */
	  @GET
	  @Path("{id}")
	  @Produces(MediaType.APPLICATION_XML)
	  public Document getTriageTag(@PathParam("id") String uid) {
			try {
				log.i("GET-uid: got GET with uid: " + uid);
				DAOTriageTag dtt = DAOTriageTagHSQLDB.getInstance();
				TriageTag tag = dtt.getTag(uid);
				if (tag == null) {
					log.i("GET-uid: cant find tag with given uid.");
					throw new WebApplicationException(404);
				} else {
					log.i("GET-uid: tag found, responding with HL7-CDA/XML.");
					log.i(tag.toString());
					return CDAParser.TriageTagObjectToXml(tag);
				}
			} catch (DAOException e) {
				log.e("DAOException: " + e.getMessage());
				throw new WebApplicationException(404);
			} catch (Exception e) {
				log.e("COULD NOT CREATE CDA-XML: " + e.getMessage());
				throw new WebApplicationException(404);
			}
	  }
	  
	  /**
	   * POST: Update of a already (in persistence) existing TriageTag. If no TriageTag with given UID exists, HTTP error 404 will be thrown.
	   * @param uid Uid of the TriageTag. PathParam-Uid and the Uid in the HL7 CDA XML must match.
	   * @param doc HL7 CDA XML, containing the TriageTag informations
	   * @param timestamp Timestamp of doc creation
	   */
	  @POST
	  @Path("{id}")
	  public Response postTriageTag(@PathParam("id") String uid, String documentStr, @Context HttpHeaders headers) {	  
		 try {
			
			String timestamp;
		    GregorianCalendar g = new GregorianCalendar();
			List<String> timestamp_header = headers.getRequestHeader("Timestamp");
			if (timestamp_header == null) {
				log.i("POST: got POST with uid: " + uid);
			} else {
				timestamp = timestamp_header.get(0);
				log.i("POST: got POST with uid: " + uid + " and timestamp " + timestamp);
				if (!checkForExistence(uid)) {
					log.e("POST: there is no CDA in the database with given uid \"" + uid + "\"");
					throw new WebApplicationException(404);
				}
			    SimpleDateFormat sdf = new SimpleDateFormat();
			    sdf.applyPattern( "yyyy-MM-dd HH:mm:ss");
			    g = new GregorianCalendar();
			    g.setTime(sdf.parse(timestamp));
			}

		    //Create Document
	        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        DocumentBuilder builder = factory.newDocumentBuilder();
	        Document doc =  builder.parse(new InputSource(new StringReader(documentStr)));
		    
		    //Persist
			TriageTag tag = CDAParser.XmlToTriageTagObject(doc);
			checkForIdMismatch(tag,uid);
			if (timestamp_header != null) {
				tag.setEffectiveTime(g);
			}
			log.i("POST: params are OK, applying changes to persistence.");
			DAOTriageTagHSQLDB.getInstance().persist(tag);
			
			return Response.status(200).entity("POST OK").build();
			
		 } catch (DAOException e) {
			 log.e("DAOException : " + e.getMessage());
		     throw new WebApplicationException(404);
		 } catch (ParseException e) {
			 log.e("Parser-Error ParseExceptio: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (ParserConfigurationException e) {
			 log.e("Parser-Error ParserConfigurationException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (SAXException e) {
			 log.e("Parser-Error SAXException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (IOException e) {
			 log.e("Parser-Error IOException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (Exception e) {
			 log.e("POST Error: " + e.getMessage());
			 e.printStackTrace();
		     throw new WebApplicationException(404);
		}
	  }
	  
	  /**
	   * PUT: Insert of a not already (in persistence) existing TriageTag. If a TriageTag with given UID exists, HTTP error 404 will be thrown.
	   * @param uid Uid of the TriageTag. PathParam-Uid and the Uid in the HL7 CDA XML must match.
	   * @param doc HL7 CDA XML, containing the TriageTag informations
	   */
	  @PUT
	  @Path("{id}")
	  public Response putTriageTag(@PathParam("id") String uid, String documentStr) {	  
		 try {
			log.i("PUT: got PUT with uid: " + uid);
			//documentStr = documentStr.replace("&gt;",">").replace("&lt;", "<");
			log.d(documentStr);
			//Error check
			if (checkForExistence(uid)) {
				log.e("PUT: there already is a CDA in the database with given uid \"" + uid + "\"");
				throw new WebApplicationException(404);
			}
			
		    //Create Document
	        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        DocumentBuilder builder = factory.newDocumentBuilder();
	        Document doc =  builder.parse(new InputSource(new StringReader(documentStr)));
			
			//Persist
			TriageTag tag = CDAParser.XmlToTriageTagObject(doc);
			checkForIdMismatch(tag,uid);
			log.i("PUT: params are OK, applying changes to persistence.");
			DAOTriageTagHSQLDB.getInstance().persist(tag);
			
			return Response.status(200).entity("PUT OK").build();
			
		 } catch (DAOException e) {
			 log.e("DAOException : " + e.getMessage());
		     throw new WebApplicationException(404);
		 } catch (ParserConfigurationException e) {
			 log.e("Parser-Error ParserConfigurationException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (SAXException e) {
			 log.e("Parser-Error SAXException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (IOException e) {
			 log.e("Parser-Error IOException: " + e.getMessage());
		     throw new WebApplicationException(404);
		} catch (Exception e) {
			 log.e("PUT Error: " + e.getMessage());
		     throw new WebApplicationException(404);
		}
	  }
	  
	  
	  /**
	   * Checks for a TriageTags existence in persistence.
	   * @param uid Uid of triagetag to look for in persistence
	   * @return True if a matching triagetag was found, false else.
	   * @throws DAOException 
	   */
	  private boolean checkForExistence(String uid) throws DAOException {
		    log.i("Checking for document existence ...");
			DAOTriageTag dtt = DAOTriageTagHSQLDB.getInstance();
			if (dtt.getTag(uid) != null) {
					log.i("Existing.");
					return true;
			}
			log.i("Not existing.");
			return false;
	  }
	  
	  /**
	   * Checks if a given TriageTags uid matches with a given string (uid)
	   * @param tag 
	   * @param uid
	   */
	  private void checkForIdMismatch(TriageTag tag, String uid) {
			if (!tag.getDocument_uid().equals(uid)) {
				log.e("Uid and given CDA-document's uid do not match: CDA-doc: " + uid + " | PUT uid: " + uid);
				throw new WebApplicationException(404);
			}
	  }
}
